package org.xbl.xchain.sdk.crypto.algo;

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERSequenceGenerator;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;

public abstract class ECAlgorithm extends Algorithm{

    public ECAlgorithm(AlgorithmType type) {
        super(type);
    }

    @Override
    public PrivateKey genPrivateKeyFromMnemonic(String mnemonic) throws Exception {
        BigInteger intKey = getBigIntegerFromMnemonic(mnemonic);
        X9ECParameters ecP = CustomNamedCurves.getByName(getType().curveName);
        ECParameterSpec spec = new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH());
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(intKey, spec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    @Override
    public PrivateKey genPrivateKeyFromPriKeyString(String privateKey) throws Exception {
        BigInteger intKey = new BigInteger(privateKey, 16);
        X9ECParameters ecP = CustomNamedCurves.getByName(getType().curveName);
        ECParameterSpec spec = new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH());
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(intKey, spec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    @Override
    public AsymmetricKeyParameter genPrivateKeyParameterFromMnemonic(String mnemonic) throws Exception {
        BigInteger intKey = getBigIntegerFromMnemonic(mnemonic);
        X9ECParameters ecP = CustomNamedCurves.getByName(getType().curveName);
        ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(getType().curveName);
        ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
                ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(intKey, dParams);
        return ecPrivateKeyParameters;
    }

    @Override
    public PublicKey genPublicKey(PrivateKey privateKey) throws Exception {
        BCECPrivateKey definingKey = (BCECPrivateKey) privateKey;
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        BigInteger d = definingKey.getD();
        ECParameterSpec ecSpec =
                definingKey.getParameters();
        ECPoint Q = definingKey.getParameters().getG().multiply(d);
        ECPublicKeySpec pubSpec = new ECPublicKeySpec(Q, ecSpec);
        PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
        return publicKeyGenerated;
    }

    @Override
    public AsymmetricKeyParameter genPubKeyParameter(AsymmetricKeyParameter privateKeyParameter) {
        ECPrivateKeyParameters ecPrivateKeyParameters = (ECPrivateKeyParameters) privateKeyParameter;
        ECPoint q = ecPrivateKeyParameters.getParameters().getG().multiply(ecPrivateKeyParameters.getD());
        return new ECPublicKeyParameters(q, ecPrivateKeyParameters.getParameters());
    }

    @Override
    byte[] encodeSignature(byte[] signature) throws Exception{
        byte[] buf = new byte[32];
        System.arraycopy(signature, 0, buf, 0, 32);
        BigInteger r = new BigInteger(1, buf);
        System.arraycopy(signature, 32, buf, 0, 32);
        BigInteger s = new BigInteger(1, buf);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DERSequenceGenerator seq = new DERSequenceGenerator(bos);
        seq.addObject(new ASN1Integer(r));
        seq.addObject(new ASN1Integer(s));
        seq.close();
        return bos.toByteArray();
    }

    @Override
    public byte[] parsePubKey(PublicKey publicKey) throws Exception {
        byte[] bytes;
        BCECPublicKey ecPublicKey = (BCECPublicKey)publicKey;
        ECPoint ecPoint = ecPublicKey.getQ();
        bytes = ecPoint.getEncoded(true);
        return bytes;
    }

}
